import { Readable } from "node:stream"

/**
 * Pixiv资源可读流
 * @description 将Pixiv的图片接口返回的ReadableStream转换为Readable实例，并实现对下游无感的自动重试的可读流
 */
export class PixivRetryReadable extends Readable {
    /**
     * 最大重试数
     * @type number
     */
    maxRetry

    /**
     * 当前重试次数
     * @type number
     */
    currentRetry

    /**
     * 远程响应
     * @type Response
     */
    response

    /**
     * 远程资源流
     * @type ReadableStream
     */
    webStream

    /**
     * 远程资源流的reader
     * @type ReadableStreamDefaultReader
     */
    reader

    /**
     * 获取远程资源流的方法
     * @type {function(): Promise<Response>}
     */
    requestRemote

    /**
     * 当前流已传输字节数
     * @type number
     */
    currentBytes

    /**
     * 已经传输给下游的字节数
     * @type number
     */
    validBytes

    /**
     * 最后一次抛出的错误
     * @type Error
     */
    lastError

    /**
     * 插件日志工具
     * @type PluginLogUtil
     */
    pluginLogUtil

    /**
     * @param requestRemote {function(): Promise<Response>}
     * @param maxRetry {number}
     * @param pluginLogUtil {PluginLogUtil}
     */
    constructor(requestRemote, maxRetry, pluginLogUtil) {
        super()
        this.maxRetry = maxRetry === undefined ? 3 : maxRetry
        this.currentRetry = 0
        this.webStream = null
        this.reader = null
        this.requestRemote = requestRemote
        this.currentBytes = 0
        this.validBytes = 0
        this.lastError = null
        this.pluginLogUtil = pluginLogUtil
    }

    async _read(size) {
        try {
            while (this.currentRetry <= this.maxRetry) {
                try {
                    if (!this.webStream) {
                        const response = await this.getResponse()
                        this.webStream = response.body
                        this.reader = this.webStream.getReader()
                    }

                    const { done, value } = await this.reader.read()
                    if (done) {
                        this.push(null) // 结束流
                        break
                    }
                    if (this.currentBytes === this.validBytes) {
                        // 已经传输给下游的字节数等于当前流已传输字节数，则全部数据推送到下游
                        this.push(value) // 推送数据到下游
                        this.validBytes += value.length
                        this.currentBytes += value.length
                        break
                    } else if (this.currentBytes < this.validBytes && this.currentBytes + value.length > this.validBytes) {
                        // 已经传输给下游的字节数大于于当前流已传输字节数，但是小于当前流已传输字节数加上当前数据块的字节数，则把数据块已经传输给下游的部分去掉，剩下的推送到下游
                        const repetitiveLength = this.validBytes - this.currentBytes
                        const validPart = value.subarray(repetitiveLength)
                        this.push(validPart) // 推送数据到下游
                        this.validBytes += validPart.length
                        this.currentBytes += value.length
                        break
                    } else {
                        // 已经传输给下游的块，跳过
                        this.currentBytes += value.length
                    }
                } catch (error) {
                    this.pluginLogUtil.error('下载时出现错误，' + error)
                    // 重试逻辑
                    this.lastError = error
                    this.currentBytes = 0
                    this.webStream = null // 清理旧流
                    this.response = null
                    this.pluginLogUtil.error(`当前已经重试${this.currentRetry}次，${this.currentRetry >= this.maxRetry ? '不再重试' : '1秒后进行下一次重试'}`)
                    await new Promise((resolve) => setTimeout(resolve, 1000))
                    this.currentRetry++
                }
            }
            if (this.currentRetry > this.maxRetry) {
                this.emit('error', this.lastError)
            }
        } catch (error) {
            this.emit('error', error)
        }
    }

    async getHeaders() {
        const response = await this.getResponse()
        return response.headers
    }

    async getResponse() {
        if (!this.response) {
            this.response = await this.requestRemote()
        }
        return this.response
    }
}
